/*
 * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
 * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

using System;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.TimeInForces;
using QuantConnect.Securities;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.CryptoFuture;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Forex;
using QuantConnect.Tests.Common.Securities;

namespace QuantConnect.Tests.Common.Orders.TimeInForces
{
    [TestFixture]
    public class TimeInForceTests
    {
        [Test]
        public void GtcTimeInForceOrderDoesNotExpire()
        {
            var security = new Equity(
                SecurityExchangeHoursTests.CreateUsEquitySecurityExchangeHours(),
                new SubscriptionDataConfig(
                    typeof(TradeBar),
                    Symbols.SPY,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                new Cash(Currencies.USD, 0, 1m),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );

            var timeInForce = new GoodTilCanceledTimeInForce();
            var order = new LimitOrder(Symbols.SPY, 10, 100, DateTime.UtcNow);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, DateTime.UtcNow, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, DateTime.UtcNow, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void DayTimeInForceEquityOrderExpiresAtMarketClose()
        {
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Equity(
                SecurityExchangeHoursTests.CreateUsEquitySecurityExchangeHours(),
                new SubscriptionDataConfig(
                    typeof(TradeBar),
                    Symbols.SPY,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                new Cash(Currencies.USD, 0, 1m),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.Day;
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.SPY, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            localTimeKeeper.UpdateTime(utcTime.AddHours(6).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            localTimeKeeper.UpdateTime(utcTime.AddHours(6));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void DayTimeInForceForexOrderBefore5PMExpiresAt5PM()
        {
            // set time to 10:00:00 AM (NY time)
            var utcTime = new DateTime(2018, 4, 25, 10, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Forex(
                SecurityExchangeHoursTests.CreateForexSecurityExchangeHours(),
                new Cash(Currencies.USD, 0, 1m),
                new Cash("EUR", 0, 0),
                new SubscriptionDataConfig(
                    typeof(QuoteBar),
                    Symbols.EURUSD,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.Day;
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.EURUSD, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            // set time to 4:59:59 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(7).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // set time to 5:00:00 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(7));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void DayTimeInForceForexOrderAfter5PMExpiresAt5PMNextDay()
        {
            // set time to 6:00:00 PM (NY time)
            var utcTime = new DateTime(2018, 4, 25, 18, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Forex(
                SecurityExchangeHoursTests.CreateForexSecurityExchangeHours(),
                new Cash(Currencies.USD, 0, 1m),
                new Cash(Currencies.EUR, 0, 0),
                new SubscriptionDataConfig(
                    typeof(QuoteBar),
                    Symbols.EURUSD,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.Day;
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.EURUSD, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            // set time to midnight (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(6));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // set time to 4:59:59 PM next day (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(23).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // set time to 5:00:00 PM next day (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(23));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        private static Security[] CryptoSecurities => new Security[]
        {
            new Crypto(
                SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
                new Cash(Currencies.USD, 0, 1m),
                new Cash("BTC", 0, 0),
                new SubscriptionDataConfig(
                    typeof(QuoteBar),
                    Symbols.BTCUSD,
                    Resolution.Minute,
                    TimeZones.Utc,
                    TimeZones.Utc,
                    true,
                    true,
                    true
                ),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            ),
            new CryptoFuture(
                Symbol.Create("BTCUSDT", SecurityType.CryptoFuture, Market.Binance),
                SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
                new Cash("USDT", 0, 1m),
                new Cash("BTC", 0, 0),
                SymbolProperties.GetDefault("USDT"),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null,
                new SecurityCache()
            )
        };

        [TestCaseSource(nameof(CryptoSecurities))]
        public void DayTimeInForceCryptoOrderExpiresAtMidnightUtc(Security security)
        {
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0);

            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.Utc);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.Day;
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(security.Symbol, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            localTimeKeeper.UpdateTime(utcTime.AddHours(14).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            localTimeKeeper.UpdateTime(utcTime.AddHours(14));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void GtdTimeInForceEquityOrderExpiresAtMarketCloseOnExpiryDate()
        {
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Equity(
                SecurityExchangeHoursTests.CreateUsEquitySecurityExchangeHours(),
                new SubscriptionDataConfig(
                    typeof(TradeBar),
                    Symbols.SPY,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                new Cash(Currencies.USD, 0, 1m),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.GoodTilDate(new DateTime(2018, 5, 1));
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.SPY, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            // April 27th before market close
            localTimeKeeper.UpdateTime(utcTime.AddHours(6).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // April 27th at market close
            localTimeKeeper.UpdateTime(utcTime.AddHours(6));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st at 10 AM
            localTimeKeeper.UpdateTime(utcTime.AddDays(4));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st before market close
            localTimeKeeper.UpdateTime(utcTime.AddDays(4).AddHours(6).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st at market close
            localTimeKeeper.UpdateTime(utcTime.AddDays(4).AddHours(6));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void GtdTimeInForceForexOrderBeforeExpiresAt5PMOnExpiryDate()
        {
            // set time to 10:00:00 AM (NY time)
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Forex(
                SecurityExchangeHoursTests.CreateForexSecurityExchangeHours(),
                new Cash(Currencies.USD, 0, 1m),
                new Cash(Currencies.EUR, 0, 0),
                new SubscriptionDataConfig(
                    typeof(QuoteBar),
                    Symbols.EURUSD,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.GoodTilDate(new DateTime(2018, 5, 1));
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.EURUSD, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            // April 27th 4:59:59 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(7).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // April 27th 5:00:00 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(7));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st at 10 AM
            localTimeKeeper.UpdateTime(utcTime.AddDays(4));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st 4:59:59 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddDays(4).AddHours(7).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st 5:00:00 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddDays(4).AddHours(7));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [TestCaseSource(nameof(CryptoSecurities))]
        public void GtdTimeInForceCryptoOrderExpiresAtMidnightUtcAfterExpiryDate(Security security)
        {
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0);

            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.Utc);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.GoodTilDate(new DateTime(2018, 5, 1));
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(security.Symbol, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            // April 27th before midnight
            localTimeKeeper.UpdateTime(utcTime.AddHours(14).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // April 28th at midnight
            localTimeKeeper.UpdateTime(utcTime.AddHours(14));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st at 10 AM
            localTimeKeeper.UpdateTime(utcTime.AddDays(4));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 1st before midnight
            localTimeKeeper.UpdateTime(utcTime.AddDays(4).AddHours(14).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // May 2nd at midnight
            localTimeKeeper.UpdateTime(utcTime.AddDays(4).AddHours(14));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void GtdSameDayTimeInForceEquityOrderExpiresAtMarketClose()
        {
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Equity(
                SecurityExchangeHoursTests.CreateUsEquitySecurityExchangeHours(),
                new SubscriptionDataConfig(
                    typeof(TradeBar),
                    Symbols.SPY,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                new Cash(Currencies.USD, 0, 1m),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.GoodTilDate(new DateTime(2018, 4, 27));
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.SPY, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            localTimeKeeper.UpdateTime(utcTime.AddHours(6).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            localTimeKeeper.UpdateTime(utcTime.AddHours(6));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void GtdSameDayTimeInForceForexOrderBefore5PMExpiresAt5PM()
        {
            // set time to 10:00:00 AM (NY time)
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Forex(
                SecurityExchangeHoursTests.CreateForexSecurityExchangeHours(),
                new Cash(Currencies.USD, 0, 1m),
                new Cash(Currencies.EUR, 0, 0),
                new SubscriptionDataConfig(
                    typeof(QuoteBar),
                    Symbols.EURUSD,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.GoodTilDate(new DateTime(2018, 4, 27));
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.EURUSD, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            // set time to 4:59:59 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(7).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // set time to 5:00:00 PM (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(7));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [Test]
        public void GtdSameDayTimeInForceForexOrderAfter5PMExpiresAt5PMNextDay()
        {
            // set time to 6:00:00 PM (NY time)
            var utcTime = new DateTime(2018, 4, 27, 18, 0, 0).ConvertToUtc(TimeZones.NewYork);

            var security = new Forex(
                SecurityExchangeHoursTests.CreateForexSecurityExchangeHours(),
                new Cash(Currencies.USD, 0, 1m),
                new Cash(Currencies.EUR, 0, 0),
                new SubscriptionDataConfig(
                    typeof(QuoteBar),
                    Symbols.EURUSD,
                    Resolution.Minute,
                    TimeZones.NewYork,
                    TimeZones.NewYork,
                    true,
                    true,
                    true
                ),
                SymbolProperties.GetDefault(Currencies.USD),
                ErrorCurrencyConverter.Instance,
                RegisteredSecurityDataTypesProvider.Null
            );
            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.NewYork);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.GoodTilDate(new DateTime(2018, 4, 27));
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(Symbols.EURUSD, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            // set time to midnight (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(6));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // set time to 4:59:59 PM next day (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(23).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            // set time to 5:00:00 PM next day (NY time)
            localTimeKeeper.UpdateTime(utcTime.AddHours(23));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }

        [TestCaseSource(nameof(CryptoSecurities))]
        public void GtdSameDayTimeInForceCryptoOrderExpiresAtMidnightUtc(Security security)
        {
            var utcTime = new DateTime(2018, 4, 27, 10, 0, 0);

            var localTimeKeeper = new LocalTimeKeeper(utcTime, TimeZones.Utc);
            security.SetLocalTimeKeeper(localTimeKeeper);

            var timeInForce = TimeInForce.GoodTilDate(new DateTime(2018, 4, 27));
            var orderProperties = new OrderProperties { TimeInForce = timeInForce };
            var order = new LimitOrder(security.Symbol, 10, 100, utcTime, "", orderProperties);

            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            var fill1 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.PartiallyFilled, OrderDirection.Buy, order.LimitPrice, 3, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));

            var fill2 = new OrderEvent(order.Id, order.Symbol, utcTime, OrderStatus.Filled, OrderDirection.Buy, order.LimitPrice, 7, OrderFee.Zero);
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));

            localTimeKeeper.UpdateTime(utcTime.AddHours(14).AddSeconds(-1));
            Assert.IsFalse(timeInForce.IsOrderExpired(security, order));

            localTimeKeeper.UpdateTime(utcTime.AddHours(14));
            Assert.IsTrue(timeInForce.IsOrderExpired(security, order));

            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill1));
            Assert.IsTrue(timeInForce.IsFillValid(security, order, fill2));
        }
    }
}
